package com.moonshine.parser.finder;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import com.adobe.ac.pmd.parser.IParserNode;
import com.adobe.ac.pmd.parser.NodeKind;
import com.adobe.ac.pmd.parser.exceptions.TokenException;
import com.moonshine.parser.models.AS3Signature;
import com.moonshine.parser.models.Flags;
import com.moonshine.utils.BenchMark;
import com.moonshine.utils.NodeUtils;
import com.moonshine.utils.SignatureTools;
import com.moonshine.utils.TopLevel;

import de.bokelberg.flex.parser.AS3Parser;

public class NodeFinder {

	private static final String TASK_DOT_COMPLETION = "dotCompletion";
	public int charIndex = 0;
	public int lineIndex = 0;
	private List<String> imports = new ArrayList<String>();
	private List<IParserNode> parents = new ArrayList<IParserNode>();
	private IResolver typeResolver;

	protected String currentFilePath;

	public NodeFinder(IResolver resolver) {
		this.typeResolver = resolver;
	}

	public CompletionContext find(String filePath, String[] lines,
			String[] classPaths) {

		return null;
	}

	final AS3Parser parser = new AS3Parser();
	private boolean resolveFromThis = false;

	public CompletionContext find(String filePath, String[] classPaths)
			throws IOException, TokenException {
		CompletionContext context = null;
		// TODO Auto-generated method stub
		currentFilePath = new File(filePath).getCanonicalPath();

		BenchMark.start("buildAST");

		IParserNode ast = parser.buildAst(currentFilePath);
		resolveFromThis = false;
		BenchMark.end("buildAST");
		IParserNode nodePackage = ast.getChild(0);
		IParserNode nodeContent = nodePackage.getChild(1);
		resolveImports(nodeContent);
		IParserNode nodeClass = nodeContent
				.getChild(nodeContent.numChildren() - 1);
		IParserNode nodeClassContent = nodeClass.getChild(nodeClass
				.numChildren() - 1);

		BenchMark.start("recurseFind");

		IParserNode node = recurseFind(nodeClassContent);

		BenchMark.end("recurseFind");
		// Completion tasks could be:
		// 'packageCompletion', 'importCompletion', 'dotCompletion',
		// 'parameterCompletion'
		// could also be 'overrideCompletion' for override statements
		String task = findCompletionTask(node, parents);

		if (task.equals(TASK_DOT_COMPLETION)) {
			// Look back and find all items involved
			BenchMark.start("findUnresolvedNodes");

			List<String> nodeChain = findUnresolvedNodes(node, parents);
			BenchMark.end("findUnresolvedNodes");
			;
			// Resolve from left-to-right
			BenchMark.start("resolveNodes");

			String clazz = resolveNodes(parents, nodeChain);

			BenchMark.end("resolveNodes");
			// Now we know the item to be completion, fetch list

			BenchMark.start("getCompletionList");
			int flags = Flags.ACCESS_PUBLIC | Flags.ACCESS_PROTECTED;
			if (resolveFromThis)
				flags = Flags.ACCESS_PRIVATE;
			List<AS3Signature> completions = getCompletionList(clazz, flags);

			BenchMark.end("getCompletionList");
			context = new CompletionContext(completions);
		}

		/*
		 * FileInputStream in = new FileInputStream(file); InputStreamReader
		 * reader = new InputStreamReader(in);
		 * 
		 * AS3ASTCompilationUnit unit =
		 * (AS3ASTCompilationUnit)parser.parse(reader); sParents.clear();
		 * LinkedListTree node =
		 * recurseFind(((ASTASPackage)unit.getPackage()).getAST());
		 */
		return context;
	}

	// Do to a FlexPMD bug with Vectors we will use an regulare expresison
	// to fix it to pass current working cases
	// https://bugs.adobe.com/jira/browse/FLEXPMD-211
	/*
	 * private String[] getScriptBlocks(String filePath) { // TODO
	 * Auto-generated method stub String content =
	 * ListUtils.join(FileUtils.readFile(new File(filePath)), "\n"); content =
	 * content.replaceAll("Vector\\.<([^>]+)>\\(\\s?\\)", "Vector.<$1>(1)");
	 * 
	 * return content.split("\\n");
	 * 
	 * }
	 */

	private void resolveImports(IParserNode nodeContent) {
		// TODO Auto-generated method stub
		imports.clear();
		for (IParserNode node : nodeContent.getChildren()) {
			if (node.is(NodeKind.IMPORT))
				imports.add(node.getStringValue());
		}

	}

	private IParserNode recurseFind(IParserNode node) {
		// TODO Auto-generated method stub
		if (node.numChildren() == 0)
			return node;

		parents.add(node);
		int nextNodeLine;
		int nextNodeCharIndex;

		int numOfChildren = node.numChildren();
		for (int i = 0; i < numOfChildren; i++) {

			IParserNode nextChild = (i + 1 < numOfChildren) ? node
					.getChild(i + 1) : null;
			IParserNode child = node.getChild(i);
			nextNodeLine = nextChild != null ? nextChild.getLine()
					: Integer.MAX_VALUE;
			nextNodeCharIndex = nextChild != null ? nextChild.getColumn()
					: Integer.MAX_VALUE;
			if (nextChild != null && nextChild.is(NodeKind.DOT)) {
				nextNodeCharIndex--;
			}
			// TODO: Check column idx too, so we don't overshoot
			// it currently ignored caret position & uses line index only
			// (moreorless)

			// In the correct block?

			// Dive as late as possible
			if (child.getLine() == lineIndex
					&& child.numChildren() == 0
					&& child.getColumn() + child.getStringValue().length() <= charIndex
					&& nextNodeCharIndex > charIndex) {
				return child;
			}
			if (child.getLine() <= lineIndex && nextNodeLine > lineIndex) {
				return recurseFind(child);
			}// &&
			/*
			 * if (child.getColumn() < charIndex && ((IParserOffsetNode)
			 * child).getEnd() > charIndex) { return recurseFind(child); }
			 */

		}
		return node;
	}

	// TODO: Figure out what kind of completion we can offer from the
	// node+parent context
	protected String findCompletionTask(IParserNode node,
			List<IParserNode> parents) {
		return "dotCompletion";
	}

	protected List<String> findUnresolvedNodes(IParserNode node,
			List<IParserNode> parents) {
		List<String> typesToResolve = new ArrayList<String>();

		for (int i = parents.size() - 1; i > 0; i--) {
			node = parents.get(i);

			if (node.is(NodeKind.DOT)) {

				typesToResolve.add(0, node.getChild(0).getStringValue());
			}

			/*
			 * TODO: return on 'call' and possibly others example:
			 * foo.bar.someFunction(this.|); // don't resolve full chain, just
			 * 'this' else if (node.kind == "call") { return; }
			 */
		}

		return typesToResolve;
	}

	// Take ['this', 'width', ...]
	protected String resolveNodes(List<IParserNode> chain,
			List<String> nodesToResolve) throws IOException {

		Collections.reverse(chain);

		String firstProperty = nodesToResolve.get(0);

		Boolean found = false;
		String foundClass = firstProperty;

		if (!firstProperty.equals("this")) {
			for (IParserNode node : chain) {
				if (node.is(NodeKind.FUNCTION)) {
					Map<String, String> params = NodeUtils
							.getParamaterList(node);
					if (params.containsKey(firstProperty)) {
						foundClass = params.get(firstProperty);
						// remove entry since it was an class
						found = true;
						break;

					}
				} else if (node.numChildren() != 0) {
					foundClass = findClassTypeByVariable(firstProperty, node);
					if (!foundClass.equals(firstProperty)) {
						found = true;
						break;
					}
				}

			}
		} else {
			found = true;
		}
		if (found) {

			nodesToResolve.remove(0);

		} else {
			foundClass = "this";
		}

		if (foundClass.equals("this") && nodesToResolve.size() == 0)
			resolveFromThis = true;
		foundClass = resolveClass(foundClass);

		return (nodesToResolve.size() != 0) ? resolveNodes(foundClass,
				nodesToResolve) : foundClass;

		/*
		 * for (IParserNode block : chain) { if (numToRemap != 0) { for (String
		 * varName : params.keySet()) { // found var name so lets get the type
		 * remapIndex = typesToResolve.indexOf(varName); if (remapIndex != -1) {
		 * --numToRemap; typesToResolve.set(remapIndex, params.get(varName));
		 * 
		 * } } }
		 * 
		 * }
		 */

	}

	private final List<IParserNode> cachedVarNodeList = new ArrayList<IParserNode>();

	protected String findClassTypeByVariable(String varName, IParserNode node) {
		List<IParserNode> children = node.getChildren();
		Collections.reverse(children);

		String clazz = varName;

		cachedVarNodeList.clear();

		NodeUtils
				.recursiveFindNodes(node, NodeKind.VAR_LIST, cachedVarNodeList);
		for (IParserNode varNode : cachedVarNodeList) {
			if (varNode.numChildren() != 0) {
				if (NodeUtils.recursiveFindNode(varNode, NodeKind.NAME)
						.getStringValue().equals(varName)) {
					clazz = NodeUtils.recursiveFindNode(varNode, NodeKind.TYPE)
							.getStringValue();
					break;

				}

			}
		}
		return clazz;
	}

	protected String resolveClass(String clazz) {
		if (!clazz.equals("this") && !TopLevel.is(clazz)) {

			for (String imp : imports) {
				if (imp.endsWith("." + clazz) || imp.endsWith(clazz)) {
					return SignatureTools.getQualifiedName(imp);
				}
			}
		}
		if (clazz.equals("this")) {
			try {
				clazz = typeResolver
						.getQualifedNameFromFilePath(currentFilePath);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} else {
			try {
				clazz = typeResolver.resolveImportedClass(currentFilePath,
						clazz);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		}
		return clazz;
	}

	protected String resolveNodes(List<String> nodesToResolve)
			throws IOException {
		return resolveNodes("this", nodesToResolve);
	}

	// Take ['this', 'width', ...]
	protected String resolveNodes(String clazz, List<String> nodesToResolve)
			throws IOException {

		// First we need to find a declaration to start resolving from
		// if (clazz.equals("this")) {
		// clazz = typeResolver.getQualifedNameFromFilePath(currentFilePath);
		// }
		String property = nodesToResolve.get(0);

		// IF it's declared for the class we can just do this,
		// otherwise we also need to look through the method it's in (if any)
		BenchMark.start("resolveNodes.lookupType (" + clazz + "," + property
				+ ")");

		String baseType = lookupType(clazz, property);

		BenchMark.end("resolveNodes.lookupType (" + clazz + "," + property
				+ ")");

		String type = baseType;
		for (int i = 1; i < nodesToResolve.size(); i++) {
			String propName = nodesToResolve.get(i);
			type = lookupType(type, propName);
		}

		return type;
	}

	// Return type as String
	// example: NodeFinder
	protected String lookupType(String clazz, String property)
			throws IOException {
		// System.out.println("Do lookup for " + property + " on " + clazz);
		// Not a top level class and not qualified

		return typeResolver.resolveType(clazz, property);

	}

	protected List<AS3Signature> getCompletionList(String forClass, int flags) {

		return typeResolver.resolveCompletionList(forClass, flags);

	}

}
